Nihilium is something you integrate, not a service you sign up for. The SDK splits a secret across processors and recovers it in a few lines of code.
It ships two high-level clients, one for sealing and one for unsealing. The cryptography, processor selection, and enforcement wiring sit behind them.
Everything else is configuration. You seal a secret across n processors and require any k of them to bring it back.
NihiliumSeal: one package per processor plus a single threshold seal. It is plain JSON, so you store it yourself. Sealing is the only paid step.The most familiar recovery gesture there is. With the ZKEmail scenario, the user replies to a recovery email and a zero-knowledge proof of that reply opens the seal. No custodian holds the secret, and only the domain of the address is ever sent, never the address itself.
Try the live demo ↗import {ZKEmailSealingClient, ZKEmailUnsealingClient,} from "@nihilium/client-sdk/scenarios/zkemail";import { NihiliumPaymentProvider } from "@nihilium/client-sdk";// Payment is charged once, upfront at sealing. Recovery is always free, without intermediaries.const sealing = await ZKEmailSealingClient.create({email: "user@example.com", threshold: 3, processorCount: 5,payment: new NihiliumPaymentProvider("https://your-server.example"),});const seal = await sealing.seal("my-super-secret-password");// Store it on the user's cloud, or keep it as the service provider:// the seal is only useful to whoever controls the email address.// Later, the user clicks "Forgot my password".const unsealing = await ZKEmailUnsealingClient.fromSeal(seal);// They reply to the recovery email; the proof does the rest.const recovered = await unsealing.unseal(); // "my-super-secret-password"
The one phase that waits on a person is the email reply, so drive your UI off it: show "check your inbox" while it is open, and everything else resolves on its own.
Strip the recovery email away and this is what a scenario wraps: seal a value across three processors so any two can recover it, using the default reveal-only policy.
import {NihiliumClient, createRevealOnlyCollection, NETWORK_IDS,} from "@nihilium/client-sdk";// Point the client at the registry network.const client = new NihiliumClient({ network: NETWORK_IDS.ARBITRUM });// A reveal-only policy: it opens the moment the reveal value is published.const { collection, template } = createRevealOnlyCollection(NETWORK_IDS.ARBITRUM);// Seal across 3 processors, any 2 required to recover (2-of-3).const sealing = await client.sealingClient({ template, threshold: 2, processorCount: 3 });const seal = await sealing.start_sealing(secret);// Store the seal anywhere: it is plain JSON.localStorage.setItem("vault", JSON.stringify(seal));// Later: recover the secret from any 2 of the 3 processors.const unsealing = await client.unsealingClient(seal, { collection });const recovered = await unsealing.start_unsealing([0, 1]);
Sealing runs one zero-knowledge proof per processor and calls a paid endpoint, so supply a payment provider for authorized processors in production. Recovery adds no cost.
The scenarios above are pre-built policies. The editor is how you build your own: drag modules from the library, wire their outputs together, and the seal opens only when the whole chain is satisfied. The graph below is the ZKEmail flow, built from three modules.
A scenario bundles an unseal policy with the client that drives it, so a caller supplies only the inputs that are genuinely theirs. Each lives under its own namespace and the set can grow without the core surface growing with it.
There is no account, no key file to guard, and no server state on your side. Store the JSON seal wherever you like; it carries everything a recovery needs, including which processors and datastreams to talk to.